#pragma once
#include <iostream>
#include <cstring>
#include <string>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>


#include "Log.hpp"
#include "InetAddr.hpp"

const static int gport = 8888;
const static int gsockfd = -1;
const static int gblcklog = 8;//这个数不要太大
enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR
};

class Sever
{
public:
    Sever(uint16_t port = gport, int sockfd = gsockfd)
    :_port(port)
    ,_listenSockfd(sockfd)
    ,_ifRunning(false)
    {}
    void Init()
    {
        //1. 创建sockfd
        _listenSockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(_listenSockfd < 0)
        {
            LOG(FATAL, "socket creat error\n");
            exit(SOCKET_ERROR);
        }
        //套接字创建成功，作为一个常规的消息记录到日志中
        LOG(INFO, "socket creat success,sockfd: %d\n", _listenSockfd);
       
        //2. 本地ip和端口号和sockfd进行绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        //给网卡配上本地的IP地址和端口号，配的IP地址和端口号是网络序列的，
        //因为到时发消息时也要将配的IP地址和端口号发给远端主机，远端主机可能回消息
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        if(bind(_listenSockfd, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            LOG(FATAL, "bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO, "bind success\n");

        //tcp是面向连接的，不是直接通信的，需要先建立连接，让客户端主机线连接我，
        //3.将sockfd设置为listen状态，未来远端的客户端可以发起连接，服务端能够做到不断获取连接
        if(listen(_listenSockfd, gblcklog) < 0)
        {
            LOG(FATAL, "set listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO, "listen success\n");
    }
    struct ThreadData
    {
        ThreadData(int sockfd, InetAddr addr, Sever* psever)
        :_sockfd(sockfd)
        ,_addr(addr)
        ,_psever(psever)
        {}
        int _sockfd;
        InetAddr _addr;
        Sever* _psever;
    };
    void Loop()
    {
        _ifRunning = true;
        //signal(SIGCHLD, SIG_IGN);
        while(_ifRunning)
        {
            //4.接受连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            //当_sockfd没有获取到一个连接时，accept会阻塞，等待_sockfd获取到链接会返回一个sockfd
            int sockfd = accept( _listenSockfd, (struct sockaddr*)&client, &len);
            if(sockfd < 0)
            {
                //接受连接失败，不至于让服务端挂掉，可以继续接受下一个链接
                //是一条警告信息
                LOG(WARNING, "accept error\n");
                continue;
            }
            InetAddr client_addr(client);
            LOG(INFO, "get a new link, client info: %s\n", client_addr.AddrStr().c_str());
            //V0-不靠谱版
            //Service(sockfd, client_addr);

            //V1-多进程版
            /*pid_t id = fork();
            if(id == 0)
            {
                //子进程执行的内容
                //子进程和父进程共用一份文件描述符表，当子进程对文件描述符表做修改时，给子进程拷贝一份文件描述附表
                //所以子进程和父进程指向相同的文件，为了防止子进程对其他文件做误操作，建议关闭子进程指向不必要的文件
                //建议子进程关闭指向_listenSockfd
                close(_listenSockfd);
                if(fork() > 0) exit(0);
                Service(sockfd, client_addr); 
                //子进程将内容处理完，不能继续执行，继续执行就是父进程的内容
                //所以处理完直接退出
                exit(0);
            }
            //文件描述附表其实是文件指针数组，空间大小是有限的，
            //所以当子进程拷贝得到自己的文件描述表时，父进程就可以关闭指向sockfd，防止内存泄漏
            close(sockfd);
            //我们这里对子进程的退出结果不关心所以第二个参数设为nullptr，
            //第三个参数设为0是阻塞式等待，设为WNOHANG是非阻塞式等待
            int n = waitpid(id, nullptr, 0);
            //如果n>0表示等待成功，等待成功向日志中打印对应的消息
            if(n > 0)
            {
                LOG(INFO, "wait child success, fd: %d\n", id);
            }*/

            //V2-多线程版
            pthread_t tid;
            //td必须是动态开辟出来的
            ThreadData* td = new ThreadData(sockfd, client_addr, this);
            pthread_create(&tid, nullptr, Execute, td);
            //如果主线程等待新线程的结束那么就变成了串行执行
            //所以将主线程和新线程进行分离，主线程不用等待新线程
            //pthread_join(tid, nullptr);
        }
        _ifRunning = false;
    }
    static void* Execute(void* args)
    {
        pthread_detach(pthread_self());//将自己和主线程分离
        //在多线程中，所有的新线程共享主线程的资源，每个新线程也都有属于自己独立的栈资源，
        //所以所有的新线程共享一份文件描述附表
        //所以多线程这里新线程不能关闭_listenSockfd
        ThreadData* td = static_cast<ThreadData*>(args);
        td->_psever->Service(td->_sockfd, td->_addr);
        delete td;
        return nullptr;
    }
    void Service(int sockfd, InetAddr client_addr)
    {
        //长服务，未来客户端不停止，服务端一直提供服务
        while(true)
        {
            //UDP是面向数据报的，无法直接进行文件读取，只能使用sendto()和recvfrom()来收发消息
            //TCP是面向字节流的，在Linux系统中一切皆文件，所以可以使用read()和write()来收发消息
            char inbuffer[1024]; 
            ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1);
            //n>0说明读到信息
            if (n > 0)
            {
                inbuffer[n] = 0;
                LOG(INFO, "get message from client %s, message: %s\n", client_addr.AddrStr().c_str(), inbuffer);

                std::string echo_string = "[server echo] #";
                echo_string += inbuffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            //n==0客户端关闭
            else if (n == 0)
            {
                LOG(INFO, "client %s quit\n", client_addr.AddrStr().c_str());
                break;
            }
            //否则read()出错
            else
            {
                LOG(ERROR, "read error: %s\n", client_addr.AddrStr().c_str());
                break;
            }
        }
        ::close(sockfd);
    }
    ~Sever()
    {}
private:
    uint16_t _port;
    int _listenSockfd;
    bool _ifRunning;
};
